/*
 * Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software
 * in a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

package org.box2dflash.dynamics.contacts {
	import org.box2dflash.dynamics.*;
	import org.box2dflash.dynamics.contacts.*;
	import org.box2dflash.collision.shapes.*;
	import org.box2dflash.collision.*;
	import org.box2dflash.common.math.*;
	import org.box2dflash.common.*;
	use namespace Internal;

	//typedef Contact* ContactCreateFcn(Shape* shape1, Shape* shape2, BlockAllocator* allocator);
	//typedef void ContactDestroyFcn(Contact* contact, BlockAllocator* allocator);
	public class Contact {
		public  function get manifolds():Array {
			return null;
		};

		/// Get the number of manifolds. This is 0 or 1 between convex shapes.
		/// This may be greater than 1 for convex-vs-concave shapes. Each
		/// manifold holds up to two contact points with a shared contact normal.
		public function get manifoldCount():int {
			return m_manifoldCount;
		}

		/// Is this contact solid?
		/// @return true if this contact should generate a response.
		public function get isSolid():Boolean {
			return (m_flags & e_nonSolidFlag) == 0;
		}

		/// Get the next contact in the world's contact list.
		public function get next():Contact {
			return m_next;
		}

		/// Get the first shape in this contact.
		public function get shape1():Shape {
			return m_shape1;
		}

		/// Get the second shape in this contact.
		public function get shape2():Shape {
			return m_shape2;
		}

		//--------------- Internals Below -------------------
	
		// m_flags
		// enum
		static public var e_nonSolidFlag:uint = 0x0001;
		static public var e_slowFlag:uint = 0x0002;
		static public var e_islandFlag:uint = 0x0004;
		static public var e_toiFlag:uint = 0x0008;

		static public function addType(createFcn:Function, destroyFcn:Function, type1:int, type2:int):void {
			//Settings.Assert(Shape.e_unknownShape < type1 && type1 < Shape.e_shapeTypeCount);
			//Settings.Assert(Shape.e_unknownShape < type2 && type2 < Shape.e_shapeTypeCount);
			s_registers[type1][type2].createFcn = createFcn;
			s_registers[type1][type2].destroyFcn = destroyFcn;
			s_registers[type1][type2].primary = true;
		
			if (type1 != type2) {
				s_registers[type2][type1].createFcn = createFcn;
				s_registers[type2][type1].destroyFcn = destroyFcn;
				s_registers[type2][type1].primary = false;
			}
		}

		static public function initializeRegisters():void {
			s_registers = new Array(Shape.e_shapeTypeCount);
			for (var i:int = 0;i < Shape.e_shapeTypeCount; i++) {
				s_registers[i] = new Array(Shape.e_shapeTypeCount);
				for (var j:int = 0;j < Shape.e_shapeTypeCount; j++) {
					s_registers[i][j] = new ContactRegister();
				}
			}
		
			addType(CircleContact.create, CircleContact.destroy, Shape.e_circleShape, Shape.e_circleShape);
			addType(PolyAndCircleContact.create, PolyAndCircleContact.destroy, Shape.e_polygonShape, Shape.e_circleShape);
			addType(PolygonContact.create, PolygonContact.destroy, Shape.e_polygonShape, Shape.e_polygonShape);
		}

		static public function create(shape1:Shape, shape2:Shape/*, allocator:* */):Contact {
			if (s_initialized == false) {
				initializeRegisters();
				s_initialized = true;
			}
		
			var type1:int = shape1.m_type;
			var type2:int = shape2.m_type;
		
			//Settings.Assert(Shape.e_unknownShape < type1 && type1 < Shape.e_shapeTypeCount);
			//Settings.Assert(Shape.e_unknownShape < type2 && type2 < Shape.e_shapeTypeCount);
			var reg:ContactRegister = s_registers[type1][type2];
			var createFcn:Function = reg.createFcn;
			if (createFcn != null) {
				if (reg.primary) {
					return createFcn(shape1, shape2/*, allocator* */);
				}
			else {
					var c:Contact = createFcn(shape2, shape1 /*, allocator*/);
					for (var i:int = 0;i < c.m_manifoldCount; ++i) {
						var m:Manifold = c.manifolds[ i ];
						m.normal = m.normal.negative;
					}
					return c;
				}
			}
		else {
				return null;
			}
		}

		static public function destroy(contact:Contact /*, allocator:* */):void {
			//Settings.Assert(s_initialized == true);
			if (contact.m_manifoldCount > 0) {
				contact.m_shape1.m_body.wakeUp();
				contact.m_shape2.m_body.wakeUp();
			}
		
			var type1:int = contact.m_shape1.m_type;
			var type2:int = contact.m_shape2.m_type;
		
			//Settings.Assert(Shape.e_unknownShape < type1 && type1 < Shape.e_shapeTypeCount);
			//Settings.Assert(Shape.e_unknownShape < type2 && type2 < Shape.e_shapeTypeCount);
			var reg:ContactRegister = s_registers[type1][type2];
			var destroyFcn:Function = reg.destroyFcn;
			destroyFcn(contact /*, allocator*/);
		}

		public function Contact(s1:Shape = null, s2:Shape = null) {
			m_flags = 0;
		
			if (!s1 || !s2) {
				m_shape1 = null;
				m_shape2 = null;
				return;
			}
		
			if (s1.isSensor || s2.isSensor) {
				m_flags |= e_nonSolidFlag;
			}
		
			m_shape1 = s1;
			m_shape2 = s2;
		
			m_manifoldCount = 0;
		
			m_friction = Math.sqrt(m_shape1.m_friction * m_shape2.m_friction);
			m_restitution = Box2dMath.max(m_shape1.m_restitution, m_shape2.m_restitution);
		
			m_prev = null;
			m_next = null;
		
			m_node1.contact = null;
			m_node1.prev = null;
			m_node1.next = null;
			m_node1.other = null;
		
			m_node2.contact = null;
			m_node2.prev = null;
			m_node2.next = null;
			m_node2.other = null;
		}

		public function update(listener:ContactListener):void {
			var oldCount:int = m_manifoldCount;
		
			evaluate(listener);
		
			var newCount:int = m_manifoldCount;
		
			var body1:Body = m_shape1.m_body;
			var body2:Body = m_shape2.m_body;
		
			if (newCount == 0 && oldCount > 0) {
				body1.wakeUp();
				body2.wakeUp();
			}
		
			// Slow contacts don't generate TOI events.
			if (body1.isStatic || body1.isBullet || body2.isStatic || body2.isBullet) {
				m_flags &= ~e_slowFlag;
			}
		else {
				m_flags |= e_slowFlag;
			}
		}

		//virtual ~Contact() {}
		public  function evaluate(listener:ContactListener):void {
		};

		static public var s_registers:Array; 
		//[][]
		static public var s_initialized:Boolean = false;
		public var m_flags:uint;
		// World pool and list pointers.
		public var m_prev:Contact;
		public var m_next:Contact;
		// Nodes for connecting bodies.
		public var m_node1:ContactEdge = new ContactEdge();
		public var m_node2:ContactEdge = new ContactEdge();
		public var m_shape1:Shape;
		public var m_shape2:Shape;
		public var m_manifoldCount:int;
		// Combined friction
		public var m_friction:Number;
		public var m_restitution:Number;
		public var m_toi:Number;
	}
}
